package com.lian.chatroom.server;

import com.lian.chatroom.protocol.MessageCodecSharable;
import com.lian.chatroom.protocol.ProtocolFrameDecoder;
import com.lian.chatroom.server.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ChatServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //自定义长度解码器,解决tcp粘包黏包问题，确保ByteBuf消息是完整的
                            pipeline.addLast(new ProtocolFrameDecoder());
                            //日志处理器
                            pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                            //自定义编解码协议
                            pipeline.addLast(new MessageCodecSharable());
                            // 用来判断是不是 读空闲时间过长，或 写空闲时间过长
                            // 5s 内如果没有收到 channel 的数据，会触发一个 IdleState#READER_IDLE 事件
                            //对三种类型的心跳检测,readerIdleTime：为读超时时间,writerIdleTime：为写超时时间
                            //主要是用来检测远端是否存活，如果不存活或活跃则对空闲Socket连接进行处理避免资源的浪费
                            pipeline.addLast(new IdleStateHandler(5, 0, 0));
                            // ChannelDuplexHandler 可以同时作为入站和出站处理器，因为有读（入站）写（出站）事件
                            pipeline.addLast(new ChannelDuplexHandler() {
                                //用来触发特殊事件，当读写空闲事件发生时，会自动触发此事件，空闲检测器
                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    // 如果触发了读空闲事件
                                    if (event.state() == IdleState.READER_IDLE) {
                                        //可能网络有问题，例如连接假死
                                        log.debug("已经 5s 没有读到数据了");
                                        ctx.channel().close();
                                    }
                                }
                            });
                            pipeline.addLast(new LoginRequestMessageHandler());
                            pipeline.addLast(new ChatRequestMessageHandler());
                            pipeline.addLast(new GroupCreateRequestMessageHandler());
                            pipeline.addLast(new GroupJoinRequestMessageHandler());
                            pipeline.addLast(new GroupMembersRequestMessageHandler());
                            pipeline.addLast(new GroupQuitRequestMessageHandler());
                            pipeline.addLast(new GroupChatRequestMessageHandler());
                            pipeline.addLast(new QuitHandler());

                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(6666).sync();
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            log.error("server error", e);
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}
